/*
 * Copyright (C) 2015 soud
 *
 * Permission is hereby granted, free of charge, to any person obtaining
 * a copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included
 * in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
 * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
 * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
 * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
 * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE
 * OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */

#include "erupt.h"
#include "ast.h"

ast_node_t *create_number(float value)
{
        ast_node_t *node = smalloc(sizeof(ast_node_t));

        node->type = TYPE_NUMBER;
        node->number.value = value;

        return node;
}

ast_node_t *create_var(char *name, ast_type_t data_type, bool mutable, ast_node_t *value)
{
        ast_node_t *node = smalloc(sizeof(ast_node_t));

        node->type = TYPE_VAR;
        node->var.name = strdup(name);
        node->var.data_type = data_type;
        node->var.mutable = mutable;
        node->var.value = value;

        return node;
}

ast_node_t *create_fn_proto(char *name, ast_node_list_t *args, ast_type_t data_type)
{
        ast_node_t *node = smalloc(sizeof(ast_node_t));

        node->type = TYPE_PROTO;
        node->prototype.name = strdup(name);
        node->prototype.data_type = data_type;

        node->prototype.args = args;

        return node;
}

ast_node_t *create_fn(ast_node_t *prototype, ast_node_list_t *body)
{
        ast_node_t *node = smalloc(sizeof(ast_node_t));

        node->type = TYPE_FN;
        node->fn.prototype = prototype;
        node->fn.body = body;

        return node;
}

ast_node_t *create_call(char *name, ast_node_list_t *args)
{
        ast_node_t *node = smalloc(sizeof(ast_node_t));

        node->type = TYPE_CALL;
        node->call.name = strdup(name);

        node->call.args = args;

        return node;
}

ast_node_t *create_if(ast_node_t *condition, ast_node_list_t *true_body,
                      ast_node_list_t *false_body)
{
        ast_node_t *node = smalloc(sizeof(ast_node_t));

        node->type = TYPE_IF;
        node->if_expr.condition = condition;
        node->if_expr.true_body = true_body;
        node->if_expr.false_body = false_body;

        return node;
}

ast_node_t *create_bin_expr(ast_op_t operator, ast_node_t *lhs, ast_node_t *rhs)
{
        ast_node_t *node = smalloc(sizeof(ast_node_t));

        node->type = TYPE_BIN_EXPR;
        node->bin_expr.operator = operator;
        node->bin_expr.lhs = lhs;
        node->bin_expr.rhs = rhs;

        return node;
}

ast_node_list_t *create_node_list(void)
{
        ast_node_list_t *nl = smalloc(sizeof(ast_node_list_t));

        nl->node = NULL;
        nl->next = NULL;

        return nl;
}

void swap_lists(ast_node_list_t *a, ast_node_list_t *b)
{
        ast_node_list_t *tmp = a;
        a = b;
        b = tmp;
}

static void destroy_node(ast_node_t *node)
{
        if (!node)
                return;

        switch(node->type) {
        case TYPE_NUMBER:
                break;
        case TYPE_VAR:
                if (node->var.name)
                        free(node->var.name);
                break;
        case TYPE_PROTO:
                if (node->prototype.name)
                        free(node->prototype.name);

                if (node->prototype.args)
                        destroy_ast(node->prototype.args);
                break;
        case TYPE_FN:
                if (node->fn.prototype)
                        destroy_node(node->fn.prototype);

                if (node->fn.body)
                        destroy_ast(node->fn.body);
                break;
        case TYPE_CALL:
                if (node->call.name)
                        free(node->call.name);

                if (node->call.args)
                        destroy_ast(node->call.args);
                break;
        case TYPE_IF:
                if (node->if_expr.condition)
                        destroy_node(node->if_expr.condition);

                if (node->if_expr.true_body)
                        destroy_ast(node->if_expr.true_body);

                if (node->if_expr.false_body)
                        destroy_ast(node->if_expr.false_body);
                break;
        case TYPE_BIN_EXPR:
                if (node->bin_expr.lhs)
                        destroy_node(node->bin_expr.lhs);
                if (node->bin_expr.rhs)
                        destroy_node(node->bin_expr.rhs);
                break;
        }

        free(node);
}

void destroy_ast(ast_node_list_t *nl)
{
        if (!nl)
                return;

        do {
                destroy_node(nl->node);
        } while ((nl = nl->next));

        free(nl);
}

static void dump_node(ast_node_t *node)
{
        if (!node)
                return;

        switch (node->type) {
        case TYPE_NUMBER:
                printf("number:\n\tvalue: %f\n", node->number.value);
                break;
        case TYPE_VAR:
                printf("variable:\n\tname: %s\n\ttype: %u\n\tmutable:%d\n",
                       node->var.name, node->var.data_type, node->var.mutable);
                break;
        case TYPE_PROTO:
                printf("proto:\n\tname: %s\n\ttype: %u\n",
                       node->prototype.name, node->prototype.data_type);
                break;
        case TYPE_FN:
                printf("function:\n");

                if (node->fn.prototype)
                        dump_node(node->fn.prototype);

                if (node->fn.body) {
                        printf("body:\n");
                        dump_node_list(node->fn.body);
                }
                break;
        case TYPE_CALL:
                /* TODO */
                break;
        case TYPE_IF:
                /* TODO */
                break;
        case TYPE_BIN_EXPR:
                /* TODO */
                break;
        }
}

void dump_node_list(ast_node_list_t *nl)
{
        do {
                dump_node(nl->node);
        } while ((nl = nl->next));
}
